热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

个子|时调_AndroidJetpack之WorkManager源码分析

篇首语:本文由编程笔记#小编为大家整理,主要介绍了AndroidJetpack之WorkManager源码分析相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android Jetpack之WorkManager源码分析相关的知识,希望对你有一定的参考价值。



android Jetpack之WorkManager源码分析

Android WorkManager简介

WorkManager 负责用来管理后台任务,它适用于需要保证系统即使应用程序退出也会运行的任务,
WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行的任务,WorkManager可以在应用程序进程的新线程中运行您的任务。如果您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 。具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager调度任务。
为什么在有了service以后,google还有出WorkManager框架呢?
1.service的滥用导致手机后台任务不断执行,耗电量大。
2.从开发者来说,Android8.0以后,Android对于后台service管理的更加严格,应用在后台启动的服务必须是前台服务,否则会导致应用crash。当然你也可以选择降低targetSdkVersion。
3.针对targetSdkVersion Google也针对的出了一些限制。ps:2019 年起,每次发布新版本后,所有应用都必须在一年内将 Target API 更新到最新版本。
官方指导地址:官方指地址


WorkManager的使用

官方DEMO 官方DEMO
2.1 Gradle依赖配置

def work = "2.1.0"
implementation"androidx.work:work-runtime:$work"
implementation"androidx.work:work-testing:$work"
// implementation"androidx.work:work-firebase:$work"
implementation"androidx.work:work-runtime-ktx:$work"

2.2 定义Worker类
自定义Worker类,继承自Worker,然后复写doWork() 方法,返回当前任务的结果 Result。doWork方法是执行在子线程的。

class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters)
override fun doWork(): Result
Log.e("workermanager","work start:")
Thread.sleep(2_000)
Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
return Result.success()

2.3 执行任务
(1)使用 OneTimeWorkRequest.Builder 创建对象Worker,将任务加入WorkManager队列。并且OneTimeWorkRequest.Builder创建的是一个单次执行的任务。
(2)将任务排入WorkManager队列,等待执行。
Worker不一定是立即执行的。WorkManager会选择适当的时间运行Worker,平衡诸如系统负载,设备是否插入等考虑因素。但是如果我们没有指定任何约束条件,WorkManager会立即运行我们的任务。

var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.build()
WorkManager.getInstance(this).enqueue(request)

2.4 重复执行任务
(1)使用PeriodicWorkRequest.Builder类创建循环任务,创建一个PeriodicWorkRequest对象
(2)然后将任务添加到WorkManager的任务队列,等待执行。
(3)最小时间间隔是15分钟。

public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.

var pRequest = PeriodicWorkRequest.Builder(JetpackWork::class.java,1,TimeUnit.SECONDS).build()
WorkManager.getInstance(this).enqueue(pRequest)

2.4 任务的状态
通过获取LiveData查看任务的状态WorkInfo.State,只有当Activityh处于活跃状态下才可以监听成功。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer
Log.e("workermanager","state :"+it?.state?.name)
)

2.5 任务约束Constraints
WorkManager 允许我们指定任务执行的环境,比如网络已连接、电量充足时等,在满足条件的情况下任务才会执行。
(1)使用Constraints.Builder()创建并配置Constraints对象,可以指定上诉任务运行时间时的约束。
(2)创建Worker时调用setConstraints指定约束条件。

var constraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true).build()

var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.setConstraints(constraint)
.build()

WorkManger提供了以下的约束作为Work执行的条件:
(1)setRequiredNetworkType:网络连接设置
(2)setRequiresBatteryNotLow:是否为低电量时运行 默认false
(3)setRequiresCharging:是否要插入设备(接入电源),默认false
(4)setRequiresDeviceIdle:设备是否为空闲,默认false
(5)setRequiresStorageNotLow:设备可用存储是否不低于临界阈值
2.6 取消任务
(1)从WorkRequest()获取Worker的ID
(2 调用WorkManager.getInstance().cancelWorkById(workRequest.id)根据ID取消任务。
WorkManager 对于已经正在运行或完成的任务是无法取消任务的。

WorkManager.getInstance(this).cancelWorkById(request.id)

2.7 添加TAG
通过为WorkRequest对象分配标记字符串来对任务进行分组

var twoRequest = OneTimeWorkRequest.Builder(JetpackTwoWork::class.java)
.setConstraints(constraint)
.addTag("jetpack")
.build()

WorkManager.getStatusesByTag() 返回该标记的所有任务的列表信息。

WorkManager.getInstance(this).getWorkInfosByTag("jetpack")

WorkManager.cancelAllWorkByTag() 取消具有特定标记的所有任务

WorkManager.getInstance(this).cancelAllWorkByTag("jetpack")

通过获取LiveData查看具有特定标记的所有任务的状态WorkInfo.State

WorkManager.getInstance(this).getWorkInfosByTagLiveData("jetpack").observe(this, Observer

)

进阶使用

3.1 数据交互
WorkManager可以将参数传递给任务,并让任务返回结果。传递和返回值都是键值对形式。
(1)使用 Data.Builder创建 Data 对象,保存参数的键值对。
(2)在创建WorkQuest之前调用WorkRequest.Builder.setInputData()传递Data的实例

var requestData = Data.Builder().putString("jetpack", "workermanager").build()
var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.setConstraints(constraint)
.setInputData(requestData)
.build()

(3 在JetpackWork.doWork方法中通过getInputData获取传递值。
(4)在构建Data对象,跟随着任务的结果返回。

class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters)
override fun doWork(): Result
Log.e("workermanager","work start:"+inputData.getString("jetpack"))
Thread.sleep(2_000)
Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
var outData = Data.Builder().putString("back","hi,jetpack").build()
return Result.success(outData)

(5) 通过LiveData监听Worker返回的数据。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer
Log.e("workermanager", "out data :" + it?.outputData?.getString("back"))
)

3.2 链式任务


  1. WorkManager允许拥有多个任务的工作序列按照顺序执行任务。
    ()使用该WorkManager.beginWith() 方法创建一个序列 ,并传递一个OneTimeWorkRequest对象;,该方法返回一个WorkContinuation对象。
    (2)使用 WorkContinuation.then()添加剩余的任务。
    (3)最后调用WorkContinuation.enqueue()将整个序列排入队列 。
    如果中间有任何任务返回 Result.failure(),则整个序列结束。并且上一个任务的结果数据可以作为下一个任务的输入数据,实现任务之间的数据传递。

WorkManager.getInstance(this).beginWith(request).then(twoRequest).then(threeRequest).enqueue()

  1. 可以使用WorkContinuation.combine()方法连接多个链来创建更复杂的序列。


要建立这个序列,先创建两个单独的链,然后将它们连接在一起成为第三个链:

WorkContinuation chainAC = WorkManager.getInstance()
.beginWith(worker A)
.then(worker C);
WorkContinuation chainBD = WorkManager.getInstance()
.beginWith(worker B)
.then(worker D);
WorkContinuation chainAll = WorkContinuation
.combine(chainAC, chainBD)
.then(worker E);
chainAll.enqueue();

在这种情况下,WorkManager在worker C之前运行workA,它也在workD之前运行workB, WorkB和workD都完成后,WorkManager 运行workE。
注意:虽然WorkManager依次运行每个子链,但不能保证链1中的任务与 链2中的任务重叠,例如,workB可能在workC之前或之后运行,或者它们可能同时运行。唯一可以保证的是每个子链中的任务将按顺序运行。
3.3 唯一的工作序列
我们可以创建一个唯一的工作序列,在任务队列里,同一个任务只存在一个,避免任务的重复执行。通过调用 beginUniqueWork() 来创建唯一的工作序列。
参数含义:1、工作序列的名称 2、当有相同名称序列时采取的策略方式 3、需要执行的Worker

WorkManager.getInstance(this).beginUniqueWork("jetpack",ExistingWorkPolicy.APPEND,request).enqueue()

ExistingWorkPolicy提供以下策略:
(1)ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列
(2)ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求
(3)ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务。


源码分析

下面我们带着三个问题来看代码,梳理一下WorkManager的源码
1.没有任务约束Constraints的任务是如何执行的。
2.添加任务约束Constraints的任务是如何被触发的。


4.1 WorkManager类


  1. 通过上面我们知道任务的执行,是通过**WorkManager.getInstance(this).enqueue(request)**执行的。
    WorkManager是个抽象类,通过WorkManager.getInstance方法返回的是,它的子类WorkManagerImpl的单例对象。
    (1)单例模式初始化WorkManagerImpl对象
    (2)调用getInstance方法返回sDelegatedInstance对象。这里sDelegatedInstance对象已经不是nulll了。下面我们就来分析一下sDelegatedInstance的初始化过程。

public static @NonNull WorkManagerImpl getInstance(@NonNull Context context)
synchronized (sLock)
WorkManagerImpl instance = getInstance();
if (instance == null)
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider)
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
else
throw new IllegalStateException("WorkManager is not initialized properly. You "
+ "have explicitly disabled WorkManagerInitializer in your manifest, "
+ "have not manually called WorkManager#initialize at this point, and "
+ "your Application does not implement Configuration.Provider.");


return instance;


public static @Nullable WorkManagerImpl getInstance()
synchronized (sLock)
if (sDelegatedInstance != null)
return sDelegatedInstance;

return sDefaultInstance;



  1. 通过反编译我们的APP,我们在AndroidManifest.xml文件中找到了一个provider的配置项。WorkManagerInitializer类又继承自ContentProvider,关于ContentProvider的启动过程这里不过多介绍,在应用的启动时候,会通过ActivityThread初始化ContentProvider(WorkManagerInitializer),即执行了onCreate方法。

android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.jandroid.multivideo.workmanager-init"
android:directBootAware="false" />

在WorkManagerInitializer的onCreate方法中调用了WorkManager.initialize的方法进行初始化。

public boolean onCreate()
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;

在WorkManager.initialize内部通过调用 WorkManagerImpl.initialize(context, configuration)完成WorkManagerImpl的初始化任务。
3. 下面重点看一下WorkManagerImpl.initializ内部做了那些初始化操作。
(1)sDelegatedInstance和sDefaultInstance都不为空,说明已经初始化过,抛出异常
(2)调用WorkManagerImpl的构造方法完成初始化任务。
(3)configuration.getTaskExecutor())内部返回默认的线程池。
(4)WorkManagerTaskExecutor内部通过调用SerialExecutor实现线程的调度。

public static void initialize(@NonNull Context context, @NonNull Configuration configuration)
synchronized (sLock)
if (sDelegatedInstance != null && sDefaultInstance != null)
throw new IllegalStateException("WorkManager is already initialized. Did you "
+ "try to initialize it manually without disabling "
+ "WorkManagerInitializer? See "
+ "WorkManager#initialize(Context, Configuration) or the class level"
+ "Javadoc for more information.");

if (sDelegatedInstance == null)
context = context.getApplicationContext();
if (sDefaultInstance == null)
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
new WorkManagerTaskExecutor(configuration.getTaskExecutor()));

sDelegatedInstance = sDefaultInstance;



private @NonNull Executor createDefaultExecutor()
return Executors.newFixedThreadPool(
// This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));


  1. initialize方法内部通过调用WorkManagerImpl的构造方法完成初始化任务。
    (1)WorkDatabase创建数据库擦操作,内部使用的是Room框架。
    (2)createSchedulers创建调度者集合。这里面主要有两种:GreedyScheduler和SystemJobScheduler(如果系统版本号大于23的话)。
    (3)创建Processor类。接下来会分析该类的作用和代码。

public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase)
Context applicationContext = context.getApplicationContext();
WorkDatabase database = WorkDatabase.create(
applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);


  1. createSchedulers主要是为了创建调度者集合。
    (1)createBestAvailableBackgroundScheduler创建一个最有效的后台调度者。
    (2)创建GreedyScheduler调度者。

public @NonNull List createSchedulers(Context context, TaskExecutor taskExecutor)
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this),
// Specify the task executor directly here as this happens before internalInit.
// GreedyScheduler creates ConstraintTrackers and controllers eagerly.
new GreedyScheduler(context, taskExecutor, this));


  1. createBestAvailableBackgroundScheduler方法
    (1)如果Android版本号>=23,返回SystemJobScheduler,内部主要是使用JobScheduler完成调度
    (2)如果手机支持GCM,则返回GcmScheduler调度者,国内基本告别了。
    (3)其他情况下返回SystemAlarmScheduler,内部使用AlarmManager实现原理。

static Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager)
Scheduler scheduler;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
else
scheduler = tryCreateGcmBasedScheduler(context);
if (scheduler == null)
scheduler = new SystemAlarmScheduler(context);
setComponentEnabled(context, SystemAlarmService.class, true);
Logger.get().debug(TAG, "Created SystemAlarmScheduler");


return scheduler;


4.2 任务的入执行队列enqueue方法


  1. 通过以上分析我们知道WorkManager.getInstance返回的是WorkManagerImpl实例,所以我们进入到enqueue方法中看看。调用WorkContinuationImpl实例的enqueue方法。

public Operation enqueue(
@NonNull List workRequests)
// This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty())
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");

return new WorkContinuationImpl(this, workRequests).enqueue();


  1. 直接进入WorkContinuationImpl.enqueue方法看看。
    (1)创建EnqueueRunnable继承自Runnable
    (2)getWorkTaskExecutor获取WorkManagerTaskExecutor对象。
    (3)通过之前在Configuration创建的线程池中执行EnqueueRunnable任务。

public @NonNull Operation enqueue()
// Only enqueue if not already enqueued.
if (!mEnqueued)
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
EnqueueRunnable runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
mOperation = runnable.getOperation();
else
Logger.get().warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));

return mOperation;


  1. EnqueueRunnable类
    在scheduleWorkInBackground方法中调度任务执行,内部调用Schedulers类的schedule方法,分配任务。

public void run()
try
if (mWorkContinuation.hasCycles())
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));

boolean needsScheduling = addToDatabase();
if (needsScheduling)
// Enable RescheduleReceiver, only when there are Worker's that need scheduling.
final Context context =
mWorkContinuation.getWorkManagerImpl().getApplicationContext();
PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground();

mOperation.setState(Operation.SUCCESS);
catch (Throwable exception)
mOperation.setState(new Operation.State.FAILURE(exception));


public void scheduleWorkInBackground()
WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
Schedulers.schedule(
workManager.getConfiguration(),
workManager.getWorkDatabase(),
workManager.getSchedulers());


  1. Schedulers类。
    在调用Scheduler的schedule的掉配任务。在分析WorkManager初始化的时候,我们知道主要有GreedyScheduler等调度类。下面重点分析一下该类。

public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List schedulers)
if (schedulers == null || schedulers.size() == 0)
return;

WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List eligibleWorkSpecs;
if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0)
WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
// Delegate to the underlying scheduler.
for (Scheduler scheduler : schedulers)
scheduler.schedule(eligibleWorkSpecsArray);




  1. GreedyScheduler类
    (1)判断时候有约束条件。没有则调用startWork执行任务。有则将任务入集合

public void schedule(@NonNull WorkSpec... workSpecs)
registerExecutionListenerIfNeeded();
// Keep track of the list of new WorkSpecs whose constraints need to be tracked.
// Add them to the known list of constrained WorkSpecs and call replace() on
// WorkConstraintsTracker. That way we only need to synchronize on the part where we
// are updating mConstrainedWorkSpecs.
List constrainedWorkSpecs &#61; new ArrayList<>();
List constrainedWorkSpecIds &#61; new ArrayList<>();
for (WorkSpec workSpec: workSpecs)
if (workSpec.state &#61;&#61; WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay &#61;&#61; 0L
&& !workSpec.isBackedOff())
if (workSpec.hasConstraints())
// Exclude content URI triggers - we don&#39;t know how to handle them here so the
// background scheduler should take care of them.
if (Build.VERSION.SDK_INT <24
|| !workSpec.constraints.hasContentUriTriggers())
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
else
Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
mWorkManagerImpl.startWork(workSpec.id);



// onExecuted() which is called on the main thread also modifies the list of mConstrained
// WorkSpecs. Therefore we need to lock here.
synchronized (mLock)
if (!constrainedWorkSpecs.isEmpty())
Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
TextUtils.join(",", constrainedWorkSpecIds)));
mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);




  1. WorkManagerImpl.startWork方法
    还是调用WorkManagerTaskExecutor的executeOnBackgroundThread方法执行StartWorkRunnable。

public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras)
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));


  1. StartWorkRunnable类
    getProcessor方法的是我们在创建WorkManager时创建的Processor对象。这里会调用Processor的startWork方法。

public void run()
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);


  1. Processor类
    内部通过构建Work的包装类WorkerWrapper&#xff0c;然后再次调用WorkManagerTaskExecutor类执行WorkerWrapper任务。

public boolean startWork(String id, WorkerParameters.RuntimeExtras runtimeExtras)
WorkerWrapper workWrapper;
synchronized (mLock)
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
if (mEnqueuedWorkMap.containsKey(id))
Logger.get().debug(
TAG,
String.format("Work %s is already enqueued for processing", id));
return false;

workWrapper &#61;
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
ListenableFuture future &#61; workWrapper.getFuture();
future.addListener(
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);

mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
return true;


  1. WorkerWrapper类
    &#xff08;1&#xff09;反射机制获取到ListenableWorker对象。其中Worker类继承自ListenableWorker类。
    &#xff08;2&#xff09;调用ListenableWorker.startWork&#xff0c;实际上是调用Worker类的startWork方法。
    &#xff08;3&#xff09;在Worker类的startWork方法中又会调用doWork方法&#xff0c;也就是我们复写的doWork方法。

public void run()
mTags &#61; mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
mWorkDescription &#61; createWorkDescription(mTags);
runWorker();

private void runWorker()
if (tryCheckForInterruptionAndResolve())
return;

mWorkDatabase.beginTransaction();
try
mWorkSpec &#61; mWorkSpecDao.getWorkSpec(mWorkSpecId);
if (mWorkSpec &#61;&#61; null)
Logger.get().error(
TAG,
String.format("Didn&#39;t find WorkSpec for id %s", mWorkSpecId));
resolve(false);
return;

// Do a quick check to make sure we don&#39;t need to bail out in case this work is already
// running, finished, or is blocked.
if (mWorkSpec.state !&#61; ENQUEUED)
resolveIncorrectStatus();
mWorkDatabase.setTransactionSuccessful();
Logger.get().debug(TAG,
String.format("%s is not in ENQUEUED state. Nothing more to do.",
mWorkSpec.workerClassName));
return;

// Case 1:
// Ensure that Workers that are backed off are only executed when they are supposed to.
// GreedyScheduler can schedule WorkSpecs that have already been backed off because
// it is holding on to snapshots of WorkSpecs. So WorkerWrapper needs to determine
// if the ListenableWorker is actually eligible to execute at this point in time.
// Case 2:
// On API 23, we double scheduler Workers because JobScheduler prefers batching.
// So is the Work is periodic, we only need to execute it once per interval.
// Also potential bugs in the platform may cause a Job to run more than once.
if (mWorkSpec.isPeriodic() || mWorkSpec.isBackedOff())
long now &#61; System.currentTimeMillis();
// Allow first run of a PeriodicWorkRequest
// to go through. This is because when periodStartTime&#61;0;
// calculateNextRunTime() always > now.
// For more information refer to b/124274584
boolean isFirstRun &#61; mWorkSpec.periodStartTime &#61;&#61; 0;
if (!isFirstRun && now Logger.get().debug(TAG,
String.format(
"Delaying execution for %s because it is being executed "
&#43; "before schedule.",
mWorkSpec.workerClassName));
// For AlarmManager implementation we need to reschedule this kind of Work.
// This is not a problem for JobScheduler because we will only reschedule
// work if JobScheduler is unaware of a jobId.
resolve(true);
return;


// Needed for nested transactions, such as when we&#39;re in a dependent work request when
// using a SynchronousExecutor.
mWorkDatabase.setTransactionSuccessful();
finally
mWorkDatabase.endTransaction();

// Merge inputs. This can be potentially expensive code, so this should not be done inside
// a database transaction.
Data input;
if (mWorkSpec.isPeriodic())
input &#61; mWorkSpec.input;
else
InputMerger inputMerger &#61; InputMerger.fromClassName(mWorkSpec.inputMergerClassName);
if (inputMerger &#61;&#61; null)
Logger.get().error(TAG, String.format("Could not create Input Merger %s",
mWorkSpec.inputMergerClassName));
setFailedAndResolve();
return;

List inputs &#61; new ArrayList<>();
inputs.add(mWorkSpec.input);
inputs.addAll(mWorkSpecDao.getInputsFromPrerequisites(mWorkSpecId));
input &#61; inputMerger.merge(inputs);

WorkerParameters params &#61; new WorkerParameters(
UUID.fromString(mWorkSpecId),
input,
mTags,
mRuntimeExtras,
mWorkSpec.runAttemptCount,
mConfiguration.getExecutor(),
mWorkTaskExecutor,
mConfiguration.getWorkerFactory());
// Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
// in test mode.
if (mWorker &#61;&#61; null)
mWorker &#61; mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mAppContext,
mWorkSpec.workerClassName,
params);

if (mWorker &#61;&#61; null)
Logger.get().error(TAG,
String.format("Could not create Worker %s", mWorkSpec.workerClassName));
setFailedAndResolve();
return;

if (mWorker.isUsed())
Logger.get().error(TAG,
String.format("Received an already-used Worker %s; WorkerFactory should return "
&#43; "new instances",
mWorkSpec.workerClassName));
setFailedAndResolve();
return;

mWorker.setUsed();
// Try to set the work to the running state. Note that this may fail because another thread
// may have modified the DB since we checked last at the top of this function.
if (trySetRunning())
if (tryCheckForInterruptionAndResolve())
return;

final SettableFuture future &#61; SettableFuture.create();
// Call mWorker.startWork() on the main thread.
mWorkTaskExecutor.getMainThreadExecutor()
.execute(new Runnable()
&#64;Override
public void run()
try
Logger.get().debug(TAG, String.format("Starting work for %s",
mWorkSpec.workerClassName));
mInnerFuture &#61; mWorker.startWork();
future.setFuture(mInnerFuture);
catch (Throwable e)
future.setException(e);

);
// Avoid synthetic accessors.
final String workDescription &#61; mWorkDescription;
future.addListener(new Runnable()
&#64;Override
&#64;SuppressLint("SyntheticAccessor")
public void run()
try
// If the ListenableWorker returns a null result treat it as a failure.
ListenableWorker.Result result &#61; future.get();
if (result &#61;&#61; null)
Logger.get().error(TAG, String.format(
"%s returned a null result. Treating it as a failure.",
mWorkSpec.workerClassName));
else
Logger.get().debug(TAG, String.format("%s returned a %s result.",
mWorkSpec.workerClassName, result));
mResult &#61; result;

catch (CancellationException exception)
// Cancellations need to be treated with care here because innerFuture
// cancellations will bubble up, and we need to gracefully handle that.
Logger.get().info(TAG, String.format("%s was cancelled", workDescription),
exception);
catch (InterruptedException | ExecutionException exception)
Logger.get().error(TAG,
String.format("%s failed because it threw an exception/error",
workDescription), exception);
finally
onWorkFinished();

, mWorkTaskExecutor.getBackgroundExecutor());
else
resolveIncorrectStatus();



小结

&#xff08;1&#xff09;Worker&#xff1a;指定我们需要执行的任务。 WorkManager API包含一个抽象的Worker类WorkManagerImpl&#xff0c;我们需要继承这个类并且在这里执行工作。
&#xff08;2&#xff09;WorkRequest&#xff1a;代表一个单独的任务。一个WorkRequest 对象指定哪个 Woker 类应该执行该任务&#xff0c;而且&#xff0c;我们还可以向 WorkRequest 对象添加详细信息&#xff0c;指定任务运行的环境等。每个 WorkRequest 都有一个自动生成的唯一ID&#xff0c;我们可以使用该ID来执行诸如取消排队的任务或获取任务状态等内容。 WorkRequest 是一个抽象类&#xff0c;在代码中&#xff0c;我们需要使用它的直接子类&#xff0c;OneTimeWorkRequest 或 PeriodicWorkRequest.。
&#xff08;3&#xff09;WorkRequest.Builder&#xff1a;用于创建WorkRequest对象的辅助类&#xff0c;同样&#xff0c;我们要使用它的一个子OneTimeWorkRequest.Builder 和PeriodicWorkRequest.Builder 。
&#xff08;4&#xff09;Constraints&#xff1a;指定任务在何时运行&#xff08;例如&#xff0c;“仅在连接到网络时”&#xff09;。我们可以通过Constraints.Builder 来创建Constraints对象&#xff0c;并在创建WorkRequest之前&#xff0c;将 Constraints 对象传递给 WorkRequest.Builder。
&#xff08;5&#xff09;WorkManager&#xff1a;将WorkRequest入队和管理WorkRequest。我们要将WorkRequest对象传递给 WorkManager &#xff0c;WorkManager 以这样的方式调度任务&#xff0c;以便分散系统资源的负载&#xff0c;同时遵守我们指定的约束条件。
&#xff08;6&#xff09;WorkStatus&#xff1a;包含有关特定任务的信息。WorkManager 为每个 WorkRequest 对象提供一个&#xff08;&#xff09;LiveData&#xff0c;LiveData持有一个WorkStatus对象&#xff0c;通过观察LiveData&#xff0c;我们可以确定任务的当前状态&#xff0c;并在任务完成后获取返回的任何值。
下面以贴一张执行的类图信息。


任务约束Constraints的任务是如何被触发


  1. 结下来分析一下如果带条件约束的任务是如何被触发的。以网络变化为例分析该场景。
    通过反编译我们的APP&#xff0c;我们在AndroidManifest.xml文件中找到了一个receiver的配置项。
    通过action&#xff0c;我们知道主要是为了监听网络的变化的。


android:name&#61;"androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:enabled&#61;"false"
android:exported&#61;"false"
android:directBootAware&#61;"false">

android:name&#61;"android.net.conn.CONNECTIVITY_CHANGE" />



  1. NetworkStateProxy类

public static class NetworkStateProxy extends ConstraintProxy


  1. ConstraintProxy类
    在ConstraintProxy类的onReceive方法中&#xff0c;startService一个SystemAlarmService&#xff0c;其中ACTION
    为ACTION_CONSTRAINTS_CHANGED。

&#64;Override
public void onReceive(Context context, Intent intent)
Logger.get().debug(TAG, String.format("onReceive : %s", intent));
Intent constraintChangedIntent &#61; CommandHandler.createConstraintsChangedIntent(context);
context.startService(constraintChangedIntent);

static Intent createConstraintsChangedIntent(&#64;NonNull Context context)
Intent intent &#61; new Intent(context, SystemAlarmService.class);
intent.setAction(ACTION_CONSTRAINTS_CHANGED);
return intent;


  1. SystemAlarmService类
    内部调用mDispatcher.add方法

&#64;Override
public int onStartCommand(Intent intent, int flags, int startId)
super.onStartCommand(intent, flags, startId);
if (mIsShutdown)
Logger.get().info(TAG,
"Re-initializing SystemAlarmDispatcher after a request to shut-down.");
// Destroy the old dispatcher to complete it&#39;s lifecycle.
mDispatcher.onDestroy();
// Create a new dispatcher to setup a new lifecycle.
initializeDispatcher();
// Set mIsShutdown to false, to correctly accept new commands.
mIsShutdown &#61; false;

if (intent !&#61; null)
mDispatcher.add(intent, startId);

// If the service were to crash, we want all unacknowledged Intents to get redelivered.
return Service.START_REDELIVER_INTENT;


  1. SystemAlarmDispatcher类
    内部调用processCommand方法

public boolean add(&#64;NonNull final Intent intent, final int startId)
Logger.get().debug(TAG, String.format("Adding command %s (%s)", intent, startId));
assertMainThread();
String action &#61; intent.getAction();
if (TextUtils.isEmpty(action))
Logger.get().warning(TAG, "Unknown command. Ignoring");
return false;

// If we have a constraints changed intent in the queue don&#39;t add a second one. We are
// treating this intent as special because every time a worker with constraints is complete
// it kicks off an update for constraint proxies.
if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action)
&& hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED))
return false;

intent.putExtra(KEY_START_ID, startId);
synchronized (mIntents)
boolean hasCommands &#61; !mIntents.isEmpty();
mIntents.add(intent);
if (!hasCommands)
// Only call processCommand if this is the first command.
// The call to dequeueAndCheckForCompletion will process the remaining commands
// in the order that they were added.
processCommand();


return true;


  1. processCommand方法
    内部调用CommandHandler.onHandleIntent方法

private void processCommand()
assertMainThread();
PowerManager.WakeLock processCommandLock &#61;
WakeLocks.newWakeLock(mContext, PROCESS_COMMAND_TAG);
try
processCommandLock.acquire();
// Process commands on the background thread.
mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable()
&#64;Override
public void run()
synchronized (mIntents)
mCurrentIntent &#61; mIntents.get(0);
if (mCurrentIntent !&#61; null)
final String action &#61; mCurrentIntent.getAction();
final int startId &#61; mCurrentIntent.getIntExtra(KEY_START_ID,
DEFAULT_START_ID);
Logger.get().debug(TAG,
String.format("Processing command %s, %s", mCurrentIntent,
startId));
final PowerManager.WakeLock wakeLock &#61; WakeLocks.newWakeLock(
mContext,
String.format("%s (%s)", action, startId));
try
Logger.get().debug(TAG, String.format(
"Acquiring operation wake lock (%s) %s",
action,
wakeLock));
wakeLock.acquire();
mCommandHandler.onHandleIntent(mCurrentIntent, startId,
SystemAlarmDispatcher.this);
catch (Throwable throwable)
Logger.get().error(
TAG,
"Unexpected error in onHandleIntent",
throwable);
finally
Logger.get().debug(
TAG,
String.format(
"Releasing operation wake lock (%s) %s",
action,
wakeLock));
wakeLock.release();
// Check if we have processed all commands
postOnMainThread(
new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this));


);
finally
processCommandLock.release();



  1. onHandleIntent方法
    onHandleIntent传入的Action 是ACTION_CONSTRAINTS_CHANGED。然后执行handleConstraintsChanged方法&#xff0c;在该方法内部经过一系列转化&#xff0c;?️会回到onHandleIntent方法中&#xff0c;而且ACTION为ACTION_DELAY_MET。

void onHandleIntent(
&#64;NonNull Intent intent,
int startId,
&#64;NonNull SystemAlarmDispatcher dispatcher)
String action &#61; intent.getAction();
if (ACTION_CONSTRAINTS_CHANGED.equals(action))
handleConstraintsChanged(intent, startId, dispatcher);
else if (ACTION_RESCHEDULE.equals(action))
handleReschedule(intent, startId, dispatcher);
else
Bundle extras &#61; intent.getExtras();
if (!hasKeys(extras, KEY_WORKSPEC_ID))
Logger.get().error(TAG,
String.format("Invalid request for %s, requires %s.",
action,
KEY_WORKSPEC_ID));
else
if (ACTION_SCHEDULE_WORK.equals(action))
handleScheduleWorkIntent(intent, startId, dispatcher);
else if (ACTION_DELAY_MET.equals(action))
handleDelayMet(intent, startId, dispatcher);
else if (ACTION_STOP_WORK.equals(action))
handleStopWork(intent, startId, dispatcher);
else if (ACTION_EXECUTION_COMPLETED.equals(action))
handleExecutionCompleted(intent, startId, dispatcher);
else
Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));





  1. DelayMetCommandHandler
    经过成成调用&#xff0c;会调用到DelayMetCommandHandler类onAllConstraintsMet方法。在该方法内部会调用startWork方法。而startWork方法正式Processor的方法。又回到了上面分析的正常work的工作流程了。

public void onAllConstraintsMet(&#64;NonNull List workSpecIds)
// WorkConstraintsTracker will call onAllConstraintsMet with list of workSpecs whose
// constraints are met. Ensure the workSpecId we are interested is part of the list
// before we call Processor#startWork().
if (!workSpecIds.contains(mWorkSpecId))
return;

synchronized (mLock)
if (mCurrentState &#61;&#61; STATE_INITIAL)
mCurrentState &#61; STATE_START_REQUESTED;
Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
// Constraints met, schedule execution
// Not using WorkManagerImpl#startWork() here because we need to know if the
// processor actually enqueued the work here.
boolean isEnqueued &#61; mDispatcher.getProcessor().startWork(mWorkSpecId);
if (isEnqueued)
// setup timers to enforce quotas on workers that have
// been enqueued
mDispatcher.getWorkTimer()
.startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
else
// if we did not actually enqueue the work, it was enqueued before
// cleanUp and pretend this never happened.
cleanUp();

else
Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));




小结

实现原理就是通过监听各种约束条件变化的广播&#xff0c;然后经过层层转化&#xff0c;最终的处理逻辑和无限制条件的work流程一致。


推荐阅读
author-avatar
君琪2010_207
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有